Skip to content

Implement an abstract Fence type to expose waiting for command buffer completion.#1007

Merged
manon-traverse merged 10 commits intollvm:mainfrom
Traverse-Research:render-backend-api-fence
Apr 9, 2026
Merged

Implement an abstract Fence type to expose waiting for command buffer completion.#1007
manon-traverse merged 10 commits intollvm:mainfrom
Traverse-Research:render-backend-api-fence

Conversation

@manon-traverse
Copy link
Copy Markdown
Contributor

@manon-traverse manon-traverse commented Mar 24, 2026

The Fence type is modeled around the DX12 Fence. The closest equivalent in Vulkan is a Timeline Semaphore. These are widely supported and included in Vulkan 1.2. Metal provides the same functionality through the SharedEvent type.

The abstract interface allows us to wait for GPU work to complete by waiting on a signal value using:

Fence->waitForCompletion(SignalValue);

Signaling the value still requires downcasting because queue submission is not done via an abstract interface yet.

@damyanp
Copy link
Copy Markdown
Contributor

damyanp commented Mar 26, 2026

@manon-traverse - is this Ready for review?

@MarijnS95
Copy link
Copy Markdown
Contributor

#987 was merged, is it time to rebase this?

@manon-traverse
Copy link
Copy Markdown
Contributor Author

@manon-traverse - is this Ready for review?

@damyanp No it's mainly waiting for the metal-cpp update PR to get merged in: #1004

@manon-traverse manon-traverse force-pushed the render-backend-api-fence branch from bcf1180 to 41f208f Compare March 31, 2026 19:39
@manon-traverse manon-traverse marked this pull request as ready for review March 31, 2026 19:40
Copy link
Copy Markdown
Contributor

@damyanp damyanp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to convince myself this is the right abstraction since I don't think anything is actually using the abstraction - as far as I can tell, there isn't anything actually using the fence through the Fence interface?

@manon-traverse
Copy link
Copy Markdown
Contributor Author

It's hard to convince myself this is the right abstraction since I don't think anything is actually using the abstraction - as far as I can tell, there isn't anything actually using the fence through the Fence interface?

The abstract is being used. However it is just being used for waiting for work completion by waiting on the signal value. Once @MarijnS95's work on command buffer submission on the queue is completed the Fence will be signaled by that function.

Currently, the API-agnostic and API-specific code is fairly intertwined. As we get more of the graphics APIs covered in the API agnostic code this will become less of a problem.

MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 1, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit + waitUntilCompleted + error check
- Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait
- DX12: CmdList Close + ExecuteCommandLists + fence signal/wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 2, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit + waitUntilCompleted + error check
- Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait
- DX12: CmdList Close + ExecuteCommandLists + fence signal/wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 2, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit + waitUntilCompleted + error check
- Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait
- DX12: CmdList Close + ExecuteCommandLists + fence signal/wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 2, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit() + waitUntilCompleted()
- Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences()
- DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 2, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit() + waitUntilCompleted()
- Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences()
- DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@bogner bogner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks reasonable, other than my usual gripe about shared_ptr

MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 7, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit() + waitUntilCompleted()
- Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences()
- DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@manon-traverse manon-traverse force-pushed the render-backend-api-fence branch from 41f208f to 921ccb0 Compare April 7, 2026 11:00
@manon-traverse manon-traverse force-pushed the render-backend-api-fence branch from 921ccb0 to 5605694 Compare April 7, 2026 11:33
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 8, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit() + waitUntilCompleted()
- Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences()
- DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MarijnS95 and others added 3 commits April 8, 2026 15:06
- Use `= default` for Fence virtual destructor (consistent with Buffer)
- Fix member initializer order in DXFence and VulkanFence to match
  declaration order (-Wreorder)
- Remove unnecessary braces in DXFence::waitForCompletion and
  VulkanDevice::createFence
- Remove spurious blank line in DXFence::waitForCompletion
- Use consistent `device_or_resource_busy` errc for VK fence errors
  instead of `not_enough_memory` / `invalid_argument /*todo*/`
- Drop unused VkResult from VulkanFence::getFenceValue
- Remove redundant zero-initialization of VkSemaphoreTypeCreateInfo
  fields already handled by `= {}`
- Use unique_ptr for InvocationState::Fence in DX and MTL (consistent
  with VK, fence is not shared)
- Drop unnecessary `this->` on createFence calls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On WSL, HANDLE is void* but the event file descriptor is an int.
Previously the code stored the fd in a HANDLE and used C-style casts to
convert back. Use the proper ifdef pattern with the native type on each
platform, matching the rest of the codebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make constructors private and expose static create() methods on
DXFence, VulkanFence, and MTLFence that return
Expected<unique_ptr<...>>. This keeps all platform-specific resource
creation encapsulated in each type, matching the pattern used for
CommandBuffer.

Each Device::createFence override now simply delegates to the
respective Fence::create(). Also adds native accessors
(getNativeFence, getNativeSemaphore, getNativeEvent) to replace direct
member access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@MarijnS95 MarijnS95 force-pushed the render-backend-api-fence branch from c4fda62 to c8afeba Compare April 8, 2026 15:28
HR::toError(IS.Fence->SetEventOnCompletion(CurrentCounter, Event),
"Failed to register end event."))
return Err;
if (auto Err = IS.Fence->waitForCompletion(CurrentCounter))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why IS.Fence is being used here, instead of F?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the goal is to use the new abstract interface. The only reason the fence is being downcast in the first place is because we don't have an abstract function for submitting on the queue yet.

uint64_t Value = 0;
[[maybe_unused]] const VkResult Ret =
vkGetSemaphoreCounterValue(Device, Semaphore, &Value);
assert(!Ret);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to && "error message" for this assert

@MarijnS95
Copy link
Copy Markdown
Contributor

LGTM

@manon-traverse manon-traverse merged commit 1522442 into llvm:main Apr 9, 2026
10 of 12 checks passed
@MarijnS95 MarijnS95 deleted the render-backend-api-fence branch April 9, 2026 14:03
MarijnS95 added a commit to Traverse-Research/offload-test-suite that referenced this pull request Apr 9, 2026
Move command buffer submission logic from each backend's Device into
Queue::submit(), which takes ownership of the command buffers. For now
it blocks internally until completion; a TODO marks that it will return
a Fence once the Fence abstraction from PR llvm#1007 is available.

- Metal: commit() + waitUntilCompleted()
- Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences()
- DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait

VulkanQueue now stores a VkDevice handle (with a TODO for lifetime
management) so it can create/destroy fences independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants